//
// Created by LiuYou on 2021/8/11.
//

#include <pthread.h>
#include <cstdlib>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <cerrno>
#include <unistd.h>
#include "Channel.hpp"
#include "ChannelList.hpp"
#include "TBF.hpp"


class TBFImpl {
public:
    int cps;
    int burst;
    int token;
    int pos;
    pthread_mutex_t mutex;
    pthread_cond_t cond;

};

using Impl = TBFImpl;

static Impl* job[TBF_MAX];
static pthread_mutex_t mutexJob = PTHREAD_MUTEX_INITIALIZER;
static pthread_once_t initOnce = PTHREAD_ONCE_INIT;
static pthread_t tid;


static int getFreePosUnlocked() {
    for (int i = 0; i < TBF_MAX; ++i) {
        if (job[i] == nullptr) {
            return i;
        }
    }
    return -1;
}

static void* threadAlarm(void* p) {
    while (true) {
        pthread_mutex_lock( &mutexJob );
        for (int i = 0; i < TBF_MAX; ++i) {
            if (job[i] != nullptr) {
                pthread_mutex_lock( &job[i]->mutex );
                job[i]->token += job[i]->cps;
                if (job[i]->token > job[i]->burst) {
                    job[i]->token = job[i]->burst;
                }
                pthread_cond_broadcast( &job[i]->cond );
                pthread_mutex_unlock( &job[i]->mutex );
            }
        }
        pthread_mutex_unlock( &mutexJob );
        sleep( 1 );
    }
}

static void moduleUnload() {
    pthread_cancel( tid );
    pthread_join( tid, nullptr );
    for (int i = 0; i < TBF_MAX; ++i) {
        free( job[i] );
    }
    return;
}

static void moduleLoad() {
    int err = pthread_create( &tid, nullptr, threadAlarm, nullptr );
    if (err) {
        fprintf( stderr, "pthread_create(): %s\n", strerror(errno));
        exit( 1 );
    }

    atexit( moduleUnload );
}

TBF* tbfInit(int cps, int burst) {
    TBFImpl* me;

    moduleLoad();
    pthread_once( &initOnce, moduleLoad );

    void* pVoid = malloc( sizeof( *me ));
    me = static_cast<TBFImpl*>(pVoid);
    if (me == nullptr) {
        return nullptr;
    }

    me->cps = cps;
    me->burst = burst;
    me->token = 0;
    pthread_mutex_init( &me->mutex, nullptr );
    pthread_cond_init( &me->cond, nullptr );


    pthread_mutex_lock( &mutexJob );
    int pos = getFreePosUnlocked();
    if (pos < 0) {
        pthread_mutex_unlock( &mutexJob );
        free( me );
//        return nullptr;
        exit( 1 );
    }
    me->pos = pos;
    job[me->pos] = me;
    pthread_mutex_unlock( &mutexJob );


    return me;
}

int tbfFetchToken(TBF* tbf, int size) {
    Impl* me = static_cast<Impl*>(tbf);

    pthread_mutex_lock( &me->mutex );
    while (me->token <= 0) {
        pthread_cond_wait( &me->cond, &me->mutex );
    }
    int n = std::min( me->token, size );
    me->token -= n;

//    pthread_cond_broadcast( &me->cond );

    pthread_mutex_unlock( &me->mutex );
    return n;
}

int tbfReturnToken(TBF* tbf, int size) {
    Impl* me = static_cast<Impl*>(tbf);

    pthread_mutex_lock( &me->mutex );
    me->token += size;
    if (me->token > me->burst) {
        me->token = me->burst;
    }

    pthread_cond_broadcast( &me->cond );
    pthread_mutex_unlock( &me->mutex );
    return 0;
}

int tbfDestroy(TBF* tbf) {
    Impl* me = static_cast<Impl*>(tbf);

    pthread_mutex_lock( &mutexJob );
    job[me->pos] = nullptr;

    pthread_mutex_unlock( &mutexJob );

    pthread_mutex_destroy( &me->mutex );
    pthread_cond_destroy( &me->cond );
    free( tbf );

    return 0;
}

